///-*-C++-*-//////////////////////////////////////////////////////////////////
//
// Hoard: A Fast, Scalable, and Memory-Efficient Allocator
//        for Shared-Memory Multiprocessors
// Contact author: Emery Berger, http://www.cs.utexas.edu/users/emery
//
// Copyright (c) 1998-2000, The University of Texas at Austin.
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as
// published by the Free Software Foundation, http://www.fsf.org.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
//////////////////////////////////////////////////////////////////////////////

/* hoardHeap, the base class for threadHeap and processHeap. */

#ifndef _HEAP_H_
#define _HEAP_H_

#include <OS.h>
#include <cstddef>

#include "config.h"

#include "arch-specific.h"
#include "superblock.h"
#include "heapstats.h"


namespace BPrivate {

class processHeap;

class hoardHeap {
	public:
		hoardHeap(void);

		// A superblock that holds more than one object must hold at least
		// this many bytes.
		enum { SUPERBLOCK_SIZE = 8192 };

		// A thread heap must be at least 1/EMPTY_FRACTION empty before we
		// start returning superblocks to the process heap.
		enum { EMPTY_FRACTION = SUPERBLOCK_FULLNESS_GROUP - 1 };

		// Reset value for the least-empty bin.  The last bin
		// (SUPERBLOCK_FULLNESS_GROUP-1) is for completely full superblocks,
		// so we use the next-to-last bin.
		enum { RESET_LEAST_EMPTY_BIN = SUPERBLOCK_FULLNESS_GROUP - 2 };

		// The number of empty superblocks that we allow any thread heap to
		// hold once the thread heap has fallen below 1/EMPTY_FRACTION
		// empty.
		enum { MAX_EMPTY_SUPERBLOCKS = EMPTY_FRACTION };

		//
		// The number of size classes.  This combined with the
		// SIZE_CLASS_BASE determine the maximum size of an object.
		//
		// NB: Once this is changed, you must execute maketable.cpp and put
		// the generated values into heap.cpp.

#if MAX_INTERNAL_FRAGMENTATION == 2
		enum { SIZE_CLASSES = 115 };
#elif MAX_INTERNAL_FRAGMENTATION == 6
		enum { SIZE_CLASSES = 46 };
#elif MAX_INTERNAL_FRAGMENTATION == 10
		enum { SIZE_CLASSES = 32 };
#else
#	error "Undefined size class base."
#endif

		// Every object is aligned so that it can always hold any type.
		enum { ALIGNMENT = HAIKU_MEMORY_ALIGNMENT };

		// ANDing with this rounds to ALIGNMENT.
		enum { ALIGNMENT_MASK = ALIGNMENT - 1 };

		// Used for sanity checking.
		enum { HEAP_MAGIC = 0x0badcafe };

		// Get the usage and allocated statistics.
		inline void getStats(int sizeclass, int &U, int &A);


#if HEAP_STATS
		// How much is the maximum ever in use for this size class?
		inline int maxInUse(int sizeclass);

		// How much is the maximum memory allocated for this size class?
		inline int maxAllocated(int sizeclass);
#endif

		// Insert a superblock into our list.
		void insertSuperblock(int sizeclass, superblock *sb, processHeap *pHeap);

		// Remove the superblock with the most free space.
		superblock *removeMaxSuperblock(int sizeclass);

		// Find an available superblock (i.e., with some space in it).
		inline superblock *findAvailableSuperblock(int sizeclass,
								block * &b, processHeap * pHeap);

		// Lock this heap.
		inline void lock(void);

		// Unlock this heap.
		inline void unlock(void);

		// Init this heap lock.
		inline void initLock(void);

		// Set our index number (which heap we are).
		inline void setIndex(int i);

		// Get our index number (which heap we are).
		inline int getIndex(void);

		// Free a block into a superblock.
		// This is used by processHeap::free().
		// Returns 1 iff the superblock was munmapped.
		int freeBlock(block * &b, superblock * &sb, int sizeclass,
				processHeap * pHeap);

		//// Utility functions ////

		// Return the size class for a given size.
		inline static int sizeClass(const size_t sz);

		// Return the size corresponding to a given size class.
		inline static size_t sizeFromClass(const int sizeclass);

		// Return the release threshold corresponding to a given size class.
		inline static int getReleaseThreshold(const int sizeclass);

		// Return how many blocks of a given size class fit into a superblock.
		inline static int numBlocks(const int sizeclass);

		// Align a value.
		inline static size_t align(const size_t sz);

	private:
		// Disable copying and assignment.

		hoardHeap(const hoardHeap &);
		const hoardHeap & operator=(const hoardHeap &);

		// Recycle a superblock.
		inline void recycle(superblock *);

		// Reuse a superblock (if one is available).
		inline superblock *reuse(int sizeclass);

		// Remove a particular superblock.
		void removeSuperblock(superblock *, int sizeclass);

		// Move a particular superblock from one bin to another.
		void moveSuperblock(superblock *,
							int sizeclass, int fromBin, int toBin);

		// Update memory in-use and allocated statistics.
		// (*UStats = just update U.)
		inline void incStats(int sizeclass, int updateU, int updateA);
		inline void incUStats(int sizeclass);

		inline void decStats(int sizeclass, int updateU, int updateA);
		inline void decUStats(int sizeclass);

		//// Members ////

		// Heap statistics.
		heapStats _stats[SIZE_CLASSES];

		// The per-heap lock.
		hoardLockType _lock;

		// Which heap this is (0 = the process (global) heap).
		int _index;

		// Reusable superblocks.
		superblock *_reusableSuperblocks;
		int _reusableSuperblocksCount;

		// Lists of superblocks.
		superblock *_superblocks[SUPERBLOCK_FULLNESS_GROUP][SIZE_CLASSES];

		// The current least-empty superblock bin.
		int _leastEmptyBin[SIZE_CLASSES];

#if HEAP_DEBUG
		// For sanity checking.
		const unsigned long _magic;
#else
#	define _magic HEAP_MAGIC
#endif

		// The lookup table for size classes.
		static size_t _sizeTable[SIZE_CLASSES];

		// The lookup table for release thresholds.
		static size_t _threshold[SIZE_CLASSES];

	public:
		static void initNumProcs(void);

	protected:
		// The maximum number of thread heaps we allow.  (NOT the maximum
		// number of threads -- Hoard imposes no such limit.)  This must be
		// a power of two! NB: This number is twice the maximum number of
		// PROCESSORS supported by Hoard.
		static int fMaxThreadHeaps;

		// number of CPUs, cached
		static int _numProcessors;
		static int _numProcessorsMask;
};



void
hoardHeap::incStats(int sizeclass, int updateU, int updateA)
{
	assert(_magic == HEAP_MAGIC);
	assert(updateU >= 0);
	assert(updateA >= 0);
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	_stats[sizeclass].incStats(updateU, updateA);
}


void
hoardHeap::incUStats(int sizeclass)
{
	assert(_magic == HEAP_MAGIC);
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	_stats[sizeclass].incUStats();
}


void
hoardHeap::decStats(int sizeclass, int updateU, int updateA)
{
	assert(_magic == HEAP_MAGIC);
	assert(updateU >= 0);
	assert(updateA >= 0);
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	_stats[sizeclass].decStats(updateU, updateA);
}


void
hoardHeap::decUStats(int sizeclass)
{
	assert(_magic == HEAP_MAGIC);
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	_stats[sizeclass].decUStats();
}


void
hoardHeap::getStats(int sizeclass, int &U, int &A)
{
	assert(_magic == HEAP_MAGIC);
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	_stats[sizeclass].getStats(U, A);
}


#if HEAP_STATS
int
hoardHeap::maxInUse(int sizeclass)
{
	assert(_magic == HEAP_MAGIC);
	return _stats[sizeclass].getUmax();
}


int
hoardHeap::maxAllocated(int sizeclass)
{
	assert(_magic == HEAP_MAGIC);
	return _stats[sizeclass].getAmax();
}
#endif	// HEAP_STATS


superblock *
hoardHeap::findAvailableSuperblock(int sizeclass,
	block * &b, processHeap * pHeap)
{
	assert(this);
	assert(_magic == HEAP_MAGIC);
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);

	superblock *sb = NULL;
	int reUsed = 0;

	// Look through the superblocks, starting with the almost-full ones
	// and going to the emptiest ones.  The Least Empty Bin for a
	// sizeclass is a conservative approximation (fixed after one
	// iteration) of the first bin that has superblocks in it, starting
	// with (surprise) the least-empty bin.

	for (int i = _leastEmptyBin[sizeclass]; i >= 0; i--) {
		sb = _superblocks[i][sizeclass];
		if (sb == NULL) {
			if (i == _leastEmptyBin[sizeclass]) {
				// There wasn't a superblock in this bin,
				// so we adjust the least empty bin.
				_leastEmptyBin[sizeclass]--;
			}
		} else if (sb->getNumAvailable() > 0) {
			assert(sb->getOwner() == this);
			break;
		}
		sb = NULL;
	}

#if 1
	if (sb == NULL) {
		// Try to reuse a superblock.
		sb = reuse(sizeclass);
		if (sb) {
			assert(sb->getOwner() == this);
			reUsed = 1;
		}
	}
#endif

	if (sb != NULL) {
		// Sanity checks:
		//   This superblock is 'valid'.
		assert(sb->isValid());
		//   This superblock has the right ownership.
		assert(sb->getOwner() == this);

		int oldFullness = sb->getFullness();

		// Now get a block from the superblock.
		// This superblock must have space available.
		b = sb->getBlock();
		assert(b != NULL);

		// Update the stats.
		incUStats(sizeclass);

		if (reUsed) {
			insertSuperblock(sizeclass, sb, pHeap);
			// Fix the stats (since insert will just have incremented them
			// by this amount).
			decStats(sizeclass,
					 sb->getNumBlocks() - sb->getNumAvailable(),
					 sb->getNumBlocks());
		} else {
			// If we've crossed a fullness group,
			// move the superblock.
			int fullness = sb->getFullness();

			if (fullness != oldFullness) {
				// Move the superblock.
				moveSuperblock(sb, sizeclass, oldFullness, fullness);
			}
		}
	}
	// Either we didn't find a superblock or we did and got a block.
	assert((sb == NULL) || (b != NULL));
	// Either we didn't get a block or we did and we also got a superblock.
	assert((b == NULL) || (sb != NULL));

	return sb;
}


int
hoardHeap::sizeClass(const size_t sz)
{
	// Find the size class for a given object size
	// (the smallest i such that _sizeTable[i] >= sz).
	int sizeclass = 0;
	while (_sizeTable[sizeclass] < sz) {
		sizeclass++;
		assert(sizeclass < SIZE_CLASSES);
	}
	return sizeclass;
}


size_t
hoardHeap::sizeFromClass(const int sizeclass)
{
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	return _sizeTable[sizeclass];
}


int
hoardHeap::getReleaseThreshold(const int sizeclass)
{
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	return _threshold[sizeclass];
}


int
hoardHeap::numBlocks(const int sizeclass)
{
	assert(sizeclass >= 0);
	assert(sizeclass < SIZE_CLASSES);
	const size_t s = sizeFromClass(sizeclass);
	assert(s > 0);
	const int blksize = align(sizeof(block) + s);
	// Compute the number of blocks that will go into this superblock.
	int nb = max_c(1, ((SUPERBLOCK_SIZE - sizeof(superblock)) / blksize));
	return nb;
}


void
hoardHeap::lock(void)
{
	assert(_magic == HEAP_MAGIC);
	hoardLock(_lock);
}


void
hoardHeap::unlock(void)
{
	assert(_magic == HEAP_MAGIC);
	hoardUnlock(_lock);
}


void
hoardHeap::initLock(void)
{
	// Initialize the per-heap lock.
	hoardLockInit(_lock, "hoard heap");
}


size_t
hoardHeap::align(const size_t sz)
{
	// Align sz up to the nearest multiple of ALIGNMENT.
	// This is much faster than using multiplication
	// and division.
	return (sz + ALIGNMENT_MASK) & ~ALIGNMENT_MASK;
}


void
hoardHeap::setIndex(int i)
{
	_index = i;
}


int
hoardHeap::getIndex(void)
{
	return _index;
}


void
hoardHeap::recycle(superblock *sb)
{
	assert(sb != NULL);
	assert(sb->getOwner() == this);
	assert(sb->getNumBlocks() > 1);
	assert(sb->getNext() == NULL);
	assert(sb->getPrev() == NULL);
	assert(hoardHeap::numBlocks(sb->getBlockSizeClass()) > 1);
	sb->insertBefore(_reusableSuperblocks);
	_reusableSuperblocks = sb;
	++_reusableSuperblocksCount;
	// printf ("count: %d => %d\n", getIndex(), _reusableSuperblocksCount);
}


superblock *
hoardHeap::reuse(int sizeclass)
{
	if (_reusableSuperblocks == NULL)
		return NULL;

	// Make sure that we aren't using a sizeclass
	// that is too big for a 'normal' superblock.
	if (hoardHeap::numBlocks(sizeclass) <= 1)
		return NULL;

	// Pop off a superblock from the reusable-superblock list.
	assert(_reusableSuperblocksCount > 0);
	superblock *sb = _reusableSuperblocks;
	_reusableSuperblocks = sb->getNext();
	sb->remove();
	assert(sb->getNumBlocks() > 1);
	--_reusableSuperblocksCount;

	// Reformat the superblock if necessary.
	if (sb->getBlockSizeClass() != sizeclass) {
		decStats(sb->getBlockSizeClass(),
			sb->getNumBlocks() - sb->getNumAvailable(),
			sb->getNumBlocks());

		sb = new((char *)sb) superblock(numBlocks(sizeclass),
			sizeclass, this);

		incStats(sizeclass,
			sb->getNumBlocks() - sb->getNumAvailable(),
			sb->getNumBlocks());
	}

	assert(sb->getOwner() == this);
	assert(sb->getBlockSizeClass() == sizeclass);
	return sb;
}

}	// namespace BPrivate

#endif // _HEAP_H_
